package island

import (
	"errors"
	"fmt"
	"github.com/Unixeno/islands/island/log"
	"github.com/Unixeno/islands/island/proxy"
	"github.com/containerd/console"
	"github.com/containerd/go-runc"
	"os"
	"path/filepath"
	"strings"
	"time"
)

func (container *base) Start() error {
	if checkStatus(container.ID, StatusRunning, StatusRemoving, StatusPaused) {
		return fmt.Errorf("invalid state %v", container.State.String())
	}
	logger.Debug("start " + "`" + container.ID[:diffLength] + "`" + " container")

	// 挂载 rootfs
	var mountErr error
	if mountErr = overlayMount(container.ID, container.ImageSetting.Path); mountErr != nil {
		logger.WithError(mountErr).Error("failed to mount rootfs layer for container")
		return mountErr
	}
	var err error
	defer func() {
		if mountErr == nil {
			if err := overlayUnmount(container.ID); err != nil {
				logger.WithError(err).WithField("id", container.ID).Error("can't unmount rootfs")
			}
		}
		if err != nil {
			if err := Remove(container.ID); err != nil {
				logger.WithError(err).WithField("id", container.ID).Error("can't remove runc state.json")
			}
		}

	}()

	IO, err := runc.NewPipeIO(os.Getuid(), os.Getegid())
	if err != nil {
		logger.WithField("id", container.ID).WithError(err).Error("failed to generate pipes")
		return nil
	}
	var consoleSocket *runc.Socket = nil
	var opts *runc.CreateOpts
	if container.RuntimeSetting.TTY {
		consoleSocket, err = runc.NewTempConsoleSocket()
		if err != nil {
			logger.WithField("id", container.ID).WithError(err).Error("failed to generate temp console socket")
			return err
		}

		opts = &runc.CreateOpts{
			IO:            IO,
			PidFile:       filepath.Join(filepath.Dir(container.Rootfs), "pid.log"),
			ConsoleSocket: consoleSocket,
		}

	} else {
		opts = &runc.CreateOpts{
			IO:      IO,
			PidFile: filepath.Join(filepath.Dir(container.Rootfs), "pid.log"),
		}
	}

	//
	// 新建 init 进程
	// Todo: cmd
	container.process = NewProcess(ProcessOptions{
		ContainerId: container.ID,
		User:        container.RuntimeSetting.User,
		WorkDir:     container.RuntimeSetting.WorkingDir,
		Command:     container.RuntimeSetting.Cmd,
	})

	startProcessErr := container.process.StartProcess(func() (int, error) {
		createErr := tool.Create(ctx, container.ID, container.Bundle, opts)
		if createErr != nil {
			return -1, runcErrHandle(IO, container.ID)
		}

		pid, err := runc.ReadPidFile(filepath.Join(filepath.Dir(container.Rootfs), "pid.log"))
		return pid, err
	})

	container.Pid = container.process.Pid
	if startProcessErr != nil {
		logger.WithField("id", container.ID).WithField("error", startProcessErr).Error("process start failed")
		return startProcessErr
	}

	// 处理容器的 IO
	if container.RuntimeSetting.TTY {
		masterEnd, err := consoleSocket.ReceiveMaster()
		if err != nil {
			logger.WithField("id", container.ID).WithError(err).Error("failed to receive pty master")
			return err
		}
		container.process.StderrReader = masterEnd
		container.process.StdinWriter = masterEnd
		container.process.StdoutReader = masterEnd
		if container.RuntimeSetting.ttyWinSize != nil {
			logger.Debug("set tty in start ", container.RuntimeSetting.ttyWinSize.Width, container.RuntimeSetting.ttyWinSize.Height)
			if err := masterEnd.Resize(*container.RuntimeSetting.ttyWinSize); err != nil {
				logger.WithField("id", container.ID).Error("failed to set tty")
			}
		}
		container.console = masterEnd
	} else {
		container.process.StderrReader = IO.Stderr()
		container.process.StdinWriter = IO.Stdin()
		container.process.StdoutReader = IO.Stdout()
	}

	logger.Debug("run prestart hook....")
	if err = container.prestartHook(); err != nil {
		logger.WithError(err).Error("run prestart hook failed")
		return err
	}

	logger.Debug("finish prestart hook, start init process...")

	err = tool.Start(ctx, container.ID)
	if err != nil {
		logger.WithField("id", container.ID).WithError(err).Error("failed to start container")
		return err
	}

	container.SetRunning()
	db.save(true, container.ID)
	return nil
}

func (container *base) GetID() string {
	return container.ID
}

func (container *base) GetName() string {
	return container.Name
}

func (container *base) Delete() error {
	if checkStatus(container.ID, StatusRunning) {
		return fmt.Errorf("can not delete %q container", container.State.String())
	}
	if err := tool.Delete(ctx, container.ID, &runc.DeleteOpts{Force: true}); err != nil {
		logger.WithField("id", container.ID).WithError(err).Error("failed to use runc to delete a container")
		return err
	}
	containerPath := filepath.Join(containerRootDir, container.ID)
	if PathExist(containerPath) {
		if err := os.RemoveAll(containerPath); err != nil {
			logger.WithField("id", container.ID).WithError(err).Error("failed to delete a container")
		}
	}
	container.SetRemoved()
	return nil
}

func (container *base) Pause() error {
	if !checkStatus(container.ID, StatusRunning) {
		return fmt.Errorf("can not pause %q container", container.State.String())
	}
	container.SetPaused()
	if err := tool.Pause(ctx, container.ID); err != nil {
		logger.WithField("id", container.ID).WithError(err).Error("failed to use runc to pause a container")
		return err
	}
	db.save(true, container.ID)
	return nil
}

func (container *base) Unpause() error {
	if !checkStatus(container.ID, StatusPaused) {
		return fmt.Errorf("can not resume %q container", container.State.String())
	}
	container.SetRunning()
	if err := tool.Resume(ctx, container.ID); err != nil {
		logger.WithField("id", container.ID).WithError(err).Error("failed to use runc to unpause a container")
		return err
	}
	db.save(true, container.ID)
	return nil
}

func (container *base) Kill(signal string, all bool) error {
	if !checkStatus(container.ID, StatusRunning) {
		return fmt.Errorf("can not kill %q container", container.State.String())
	}
	opts := runc.KillOpts{All: all}
	signalTable := map[string]int{
		"SIGKILL": 9,
		"SIGTERM": 15,
	}
	if _, ok := signalTable[signal]; !ok {
		signal = "SIGKILL"
	}
	if err := tool.Kill(ctx, container.ID, signalTable[signal], &opts); err != nil {
		logger.WithField("id", container.ID).WithError(err).Error("failed to use runc to kill a container")
		return err
	}
	return nil
}

func (container *base) GetImageID() string {
	return container.ImageSetting.ImageID
}

func (container *base) Inspect() base {
	return *container
}

func (container *base) prestartHook() error {
	logger.Debug("configure network...")
	// 配置网络
	if err := container.configNetwork(); err != nil {
		return err
	}
	logger.Debug("container network configured")

	// 配置 logger
	if streamLogger, err := log.NewStreamLogger(filepath.Join(containerRootDir, container.ID)); err != nil {
		return err
	} else {
		container.logs = streamLogger
	}

	container.logs.Start()

	// 配置proxy
	if len(container.RuntimeSetting.Networks) > 0 && len(container.RuntimeSetting.ExposedPorts) > 0 {
		dstIP := strings.Split(container.RuntimeSetting.Networks[0].Address[0], "/")[0]
		container.RuntimeSetting.ProxyManager = proxy.GetProxyManager()
		for _, exposedPort := range container.RuntimeSetting.ExposedPorts {
			container.RuntimeSetting.ProxyManager.Add(exposedPort.Src, dstIP+":"+exposedPort.DstPort, exposedPort.Protocol)
		}
		errs := container.RuntimeSetting.ProxyManager.StartAll()
		if len(errs) > 0 {
			for _, err := range errs {
				logger.WithError(err).Error("failed to start proxy")
			}
		}
	}

	go stdoutHandle(container)

	return nil
}

// 容器停止后自动执行
func (container *base) poststopHook() {
	if err := tool.Delete(ctx, container.ID, &runc.DeleteOpts{Force: true}); err != nil {
		logger.WithField("id", container.ID).WithError(err).Error("failed to use runc to delete a container in poststopHook")
	}

	if container.RuntimeSetting.ProxyManager != nil {
		errs := container.RuntimeSetting.ProxyManager.StopAll()
		if len(errs) > 0 {
			for _, err := range errs {
				logger.WithError(err).Error("failed to stop proxy")
			}
		}
	}

	container.SetStopped()
	container.Pid = 0
	container.ExitCode = container.process.ExitCode
	db.save(true, container.ID)
	// 关闭 logger
	container.logs.Close()
	container.process = nil
}

func (container *base) Stop(timeout int) error {

	if !checkStatus(container.ID, StatusRunning) {
		logger.WithField("id", container.ID).Error("cannot stop a not running container")
		return errors.New("cannot stop a not running container")
	}

	if err := container.Kill("SIGTERM", true); err != nil {
		logger.WithField("id", container.ID).WithField("error", err).Error("cannot kill the container by SIGTERM")
		return err
	}

	exitChan := container.State.WaitStop()

	select {
	case <-exitChan:
		logger.WithField("id", container.ID).WithField("signal", "SIGTERM").Debug("container has been terminated")
		return nil
	case <-time.After(time.Duration(timeout) * time.Second):
		if err := container.Kill("SIGKILL", true); err != nil {
			logger.WithField("id", container.ID).WithField("error", err).Error("cannot kill the container by SIGKILL")
		} else {
			logger.WithField("id", container.ID).WithField("signal", "SIGKILL").Debug("container has been killed")
		}
		<-exitChan
	}
	return nil
}

func (container *base) ResizeTTY(width, height uint16) error {
	// 重新设置 console
	if container.console != nil {
		if err := container.console.Resize(console.WinSize{Height: height, Width: width}); err != nil {
			return err
		}
	}
	return nil
}

func (container *base) GetRWLayer() string {
	return filepath.Join(containerRootDir, container.ID, "upper")
}

func (container *base) GetLogs() *log.StreamLogger {
	return container.logs
}

func (container *base) SetExecInstance(instance *execInstance) {
	container.ExecInstances = append(container.ExecInstances, instance)
}

func (container *base) GetExitCode() int {
	return container.ExitCode
}

func (container *base) Restart(timeout int) error {
	if checkStatus(container.ID, StatusRunning) {
		if err := container.Stop(timeout); err != nil {
			return err
		}
	}

	container.SetRestarting()
	db.save(true, container.ID)
	// 重启容器
	if err := container.Start(); err != nil {
		return err
	}

	return nil
}
